# -*- coding: utf-8 -*-
from hashlib import md5
from datetime import datetime, timedelta

from future.utils import raise_with_traceback
from sqlalchemy.exc import SQLAlchemyError

from common.admin.model import (User, Permission,
                                UserToken, ROLE, Record, UserChannel)
from common.admin.model import *
from common.utils import exceptions as err
from common.utils.respcode import StatusCode
from common.utils import id_generator
from common.utils.db import (get_orderby, parse_query_dct, list_object,
                             paginate, generate_filter, upsert, get_count)

_SALT = u"%#fEg*PY"


def encode_password(passwd):
    return md5(passwd.encode('utf-8') + _SALT).hexdigest()


def sql_wrapper(func):
    def _wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except SQLAlchemyError as e:
            orm.session.rollback()
            raise_with_traceback(err.DbError(e))
        except err.Error:
            orm.session.rollback()
            raise
        except Exception as e:
            orm.session.rollback()
            raise_with_traceback(err.Error(e))
        finally:
            orm.session.close()

    return _wrapper


@sql_wrapper
def create_user(email, password, nickname='',
                role=ROLE.FORBIDDEN):
    user = User()
    user.email = email
    user.password = encode_password(password)
    user.nickname = nickname
    user.role = role
    user.created_at = user.updated_at = datetime.utcnow()

    user.save()


@sql_wrapper
def update_user(user_id, user_info):
    user = User.query.with_for_update().filter(
        User.id == user_id).first()
    if not user:
        raise_with_traceback(err.AuthenticateError(
            status=StatusCode.INVALID_USER))

    if 'password' in user_info:
        user_info['password'] = encode_password(user_info['password'])

    for k, v in user_info.iteritems():
        setattr(user, k, v)

    user.save()
    return user


@sql_wrapper
def list_users(query_dct):
    query_dct['deleted'] = str(USER_NOT_DELETED)

    # user can only see lower level users
    query_dct = parse_query_dct(query_dct, User)
    query = User.query.filter(generate_filter(query_dct, User))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), User)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def delete_role(role_id):
    user = User.query.filter(User.role == role_id).all()
    if user:
        raise err.ParamError('User in this role')
    Role.query.filter(Role.id == role_id).update({
        'deleted': 1,
        'mch_ids': '',
        'permissions': ''
    })
    orm.session.commit()
    return {}


@sql_wrapper
def get_user(user_id):
    return User.query.filter(User.id == user_id).first()


@sql_wrapper
def get_mchids_by_user(user_id):
    user = User.query.filter(User.id == user_id).first()
    if user is None:
        return []
    if user.mch_ids is None:
        return []
    return user.mch_ids.split(',')


@sql_wrapper
def get_mchids_filter_by_user(user_id):
    user = User.query.filter(User.id == user_id).first()
    if not user:
        return None
    role = Role.query.filter(Role.id == user.role).first()
    if not role:
        return None
    if not role.mch_ids:
        return None
    return {"$in": [int(m) for m in role.mch_ids.split(',')]}


@sql_wrapper
def login_user(email, password):
    user = User.query.filter(User.email == email).first()
    if not user:
        raise err.AuthenticateError(status=StatusCode.INVALID_USER)

    if user.password != encode_password(password):
        raise err.AuthenticateError(status=StatusCode.WRONG_PASSWORD)

    UserToken.query.filter(UserToken.user_id == user.id).delete()

    user_token = UserToken()
    user_token.token = id_generator.generate_uuid()
    user_token.deleted = 0
    user_token.user_id = user.id
    user_token.save()
    orm.session.commit()

    user_info = user.as_dict()
    user_info['token'] = user_token.token
    user_info.pop('password', '')
    return user_info


@sql_wrapper
def logout_device(user_id, token):
    UserToken.query.filter(
        UserToken.user_id == user_id).filter(UserToken.token == token).update(
        {'deleted': 1})

    orm.session.commit()


@sql_wrapper
def logout_user(user_id):
    UserToken.query.filter(
        UserToken.user_id == user_id).update({'deleted': 1})
    orm.session.commit()


@sql_wrapper
def get_online_info(user_id, token):
    return UserToken.query.filter(
        UserToken.user_id == user_id).filter(UserToken.token == token).first()


@sql_wrapper
def check_token_vaild(user_id, token):
    t = UserToken.query.filter(
        UserToken.user_id == user_id).filter(UserToken.token == token).filter(UserToken.deleted != 1).first()
    if not t:
        return False
    if datetime.now() > t.updated_at + timedelta(minutes=20):
        return False
    t.updated_at = datetime.now()
    t.save()
    UserToken.query.filter(
        UserToken.user_id == user_id).filter(UserToken.token != token).update(
        {'deleted': 1})
    orm.session.commit()
    return True


@sql_wrapper
def list_perm(query_dct):
    return list_object(query_dct, Permission)


@sql_wrapper
def create_perm(url, permission, min_role):
    perm = Permission()
    perm.url = url
    perm.permission = permission
    perm.min_role = min_role
    perm.created_at = perm.updated_at = datetime.utcnow()
    perm.save()
    return perm


@sql_wrapper
def get_perm(url=None, perm=None, id=None):
    if id:
        return Permission.query.filter(
            Permission.id == id).first()
    elif url and perm:
        return Permission.query.filter(
            Permission.url == url).filter(
            Permission.permission == perm).first()


@sql_wrapper
def update_perm(id, min_role):
    perm = Permission.query.filter(
        Permission.id == id).with_for_update().first()
    if perm:
        perm.min_role = min_role
        perm.save()


@sql_wrapper
def insert_record(info):
    return upsert(Record, info)


@sql_wrapper
def list_record(query_dct):
    email = query_dct.pop('email', None)
    query = orm.session.query(Record, User.email).join(
        User, Record.operator == User.id)
    if email is not None:
        query = query.filter(User.email.like('%%%s%%' % email))
    query_dct = parse_query_dct(query_dct, Record)
    query = query.filter(generate_filter(query_dct, Record))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), Record)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def filter_user_channels(user_id, d, key):
    items = UserChannel.query.filter(UserChannel.user_id == user_id).all()
    l = []
    for i in items:
        l.append(i.channel_id)
    if len(l) > 0:
        d[key] = {"$in": l}


@sql_wrapper
def add_user_channel(user_id, channel_id):
    user_channel = UserChannel()
    user_channel.user_id = user_id
    user_channel.channel_id = channel_id
    user_channel.save()
    return user_channel


@sql_wrapper
def create_role(rolename, permissions, mch_ids):
    role = Role()
    role.rolename = rolename
    role.permissions = ','.join([str(p) for p in permissions])
    role.mch_ids = mch_ids
    role.deleted = USER_NOT_DELETED
    role.enabled = USER_ENABLE
    role.save()
    return role


@sql_wrapper
def list_roles(query_dct):
    query_dct['deleted'] = str(USER_NOT_DELETED)

    # user can only see lower level users
    query_dct = parse_query_dct(query_dct, Role)
    query = Role.query.filter(generate_filter(query_dct, Role))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), Role)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def get_role(role_id):
    role = Role.query.filter(Role.id == role_id).first()
    if not role:
        raise err.DataError('role id %s not exist' % role_id)
    return role


@sql_wrapper
def update_role(role_id, role_info):
    role = Role.query.with_for_update().filter(
        Role.id == role_id).first()
    if not role:
        raise err.ParamError('role not exist')

    for k, v in role_info.iteritems():
        if k in ['permissions', ]:
            v = ','.join([str(i) for i in v])
        setattr(role, k, v)

    role.save()
    return role


@sql_wrapper
def delete_user(user_id):
    UserToken.query.filter(UserToken.user_id == user_id).delete()
    User.query.filter(User.id == user_id).update({'deleted': 1, 'role': 0})
    orm.session.commit()


@sql_wrapper
def list_perm_mod():
    return PERMISSION_MOD.to_dict()


@sql_wrapper
def get_perm_id(url, permission):
    return Permission.query.filter(
        Permission.url == url).filter(
        Permission.permission == permission).first()
